什么数据适合放在 store 中?
我们可以通过数据持久度和数据消费程度来区分数据类型:
数据持久度
- 快速变更型
- 中等持续型
- 长远稳定型
快速变更型:这类数据往往代表着短时间内快速变更,比如文本框内容可能随着用户输入持续变化,或者快速拖动变更位置等,这类数据更加适合维护在
state
中。中等持续性:当用户浏览或者使用应用时,在刷新页面之前数据保持相对稳定,比如
ajax
获取数据,编辑form
表单等,这类数据比较通用,可能被其他组件所应用。这类数据适合通过Redux store
维护,再通过connect
被组件使用。长远稳定型:指在页面多次刷新或者多次访问期间都保持不变的数据,这类数据通常不会放在
Redux
里面维护,一般会放到localstorage
或者db
里面。数据消费程度
数据特性体现在消费层面,即有多少组件需要使用。越多组件消费的数据,就应该放在
Redux store
里面维护,反之,当数据只服务于单一组件时,由React state
维护就更加合理。
假设现在将所有状态都放置在 Redux store
中管理:
- 对于简单应用的状态,
React state
管理已经绰绰有余,如果将所有状态放在Redux store
中会带来额外的开发成本 - 对于复杂应用的状态,将所有状态都放到
Redux store
中管理,会使Redux store
状态树变得十分臃肿,维护变得十分困难,另一方面,对于层级较深的组件,它们与Redux store
中的状态通信会变得复杂。
综上所述,不建议将所有状态都托管于 Redux store
。
数据不可变 (immutable
or immer
)
不可变数据对于 React
组件的意义不再赘述。这里想谈下两个优秀的库 immutable
和 immer
。
immutable
对象与原生的 js 对象操作有很大异同,加上繁琐的 fromJS 和 toJS 操作,会在一定程度上提升开发成本,并且 immutable
对项目侵入程度较大,会增加项目后期的维护成本。
可以考虑用支持原生 js 对象操作的 immer
库代替 immutable
。
数据扁平化
业务数据的原子性与原始性
- 原子性
保证业务数据的原子性是指不要将业务数据与 ui 数据或者其他业务数据(不同接口返回的数据)耦合,即不要将 ui 状态与其他业务数据掺杂进某个业务数据中。由于不同业务数据之间、业务数据与 ui 状态数据之间拥有不同的生命周期,如果混合使用,会给开发带来额外的开发和维护成本,举两个例子:
|
|
|
|
原始性
保证业务数据的原始性是指存放在Redux store
中的数据应该是后端接口返回的原始数据,前端开发者不在Redux
层面对其作任何处理,这么做的好处在于,原始的业务数据对不同组件的适配性更强,比如组件 A 需要一个正向排序的列表,组件 B 却需要一个逆向排序的列表,组件 C 则需要未排序的原始列表,较好的做法是分别在组件 A 和 组件 B 中对原始列表进行相应的排序,组件 C 则直接使用原始列表即可。上述组件 A、B、C 依赖于不同排序的数据列表,保证
Redux store
中该数据列表的原始性是比较好的做法,即使现在只有 A 组件需要正向排序后的数据列表,我也建议开发者不要将排序后的列表存于Redux store
中,因为无法保证后续的需求迭代不会出现其他诸如 B、C 的组件依赖该列表,而且依赖方式可能各有不同。结论:保证业务数据的原始性有助于业务数据去适配对数据要求不一样的组件,适配逻辑可以考虑放到
mapStateToProps
函数中处理。
数据逻辑在哪写?
在 React-Redux
应用中,有四处地方可以写数据逻辑:
React
组件内部action-creator
函数内部reducer
函数内部connect
函数内部React
组件内部写逻辑:如果组件内部需要维护state
,那么很可能会涉及到state
的逻辑处理;而对于没有state
可维护的组件而言,拿到手的props
不一定可以直接适配render
组件树需要的数据,因此需要对其进行衍生计算;甚至对于一些 需要在action-creator
内部处理的逻辑也可以在组件内部进行消化。action-creator
函数内部和reducer
函数内部写逻辑:action-creator
比reducer
数据处理能力更强。得益于中间件,action-creator
可以感知整个Redux store
,并且可以处理异步逻辑;而reducer
只能获取自身维护的state
,并且受限于纯函数无法处理异步逻辑,reducer
适合处理接收相同action
后必须执行的通用逻辑,对于reducer
可能出现过于冗长函数的问题,可以考虑使用type-to-reducer
库来对不同action
对应处理逻辑进一步拆分。connect
函数内部写逻辑:connect
作为Redux store
和React
组件的胶合层,可以方便React
组件获取Redux store
衍生计算(通常在mapStateToProps
函数中计算)后的状态,相比于在render
函数中写Redux store
衍生计算逻辑,在mapStateToProps
函数中写逻辑可以在一定程度上将该部分逻辑与render
的视图分离,分层更加清晰,并且可以通过reselect
库可以缓存计算结果。
无论在哪写数据逻辑都应该遵循代码简洁、可读、可维护、可扩展的原则。
不建议将所有数据逻辑集中写在上述任何一个地方,如果你将数据合理的划分到组件自身的 state
和 Redux
的 reduer
中,自然地,数据逻辑处理是分散的,不会集中在任何一个地方。